﻿using JESAI.DistributedLock.Internal;
using JESAI.DistributedLock.Internal.Data;
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.DistributedLock.Oracle;

/// <summary>
/// 基于DBMS_lock包为Oracle数据库实现分布式锁
/// </summary>
public sealed partial class OracleDistributedLock : IInternalDistributedLock<OracleDistributedLockHandle>
{
    internal const int MaxNameLength = 128;

    private readonly IDbDistributedLock _internalLock;

    /// <summary>
    /// 构造具有给定<paramref name="name"/>的锁，该锁使用提供的<paramref name="connectionString"/>进行连接，并
    ///<paramref name=“options”/>。
    ///
    /// 除非指定<paramref name="exactNam"”/>，否则将对<paramref name="name"/>进行转义/哈希处理，以确保名称有效性。
    /// </summary>
    public OracleDistributedLock(string name, string connectionString, Action<OracleConnectionOptionsBuilder>? options = null, bool exactName = false)
        : this(name, exactName, n => CreateInternalLock(n, connectionString, options))
    {
    }

    /// <summary>
    /// 使用给定的<paramref name="name"/>构造一个锁，该锁使用提供的<pararmref name="connection"/>进行连接。
    ///
    ///除非指定<paramref name="exactName"/>，否则将对<paramref name="name"/>进行转义/哈希处理，以确保名称有效性。
    /// </summary>
    public OracleDistributedLock(string name, IDbConnection connection, bool exactName = false)
        : this(name, exactName, n => CreateInternalLock(n, connection))
    {
    }

    private OracleDistributedLock(string name, bool exactName, Func<string, IDbDistributedLock> internalLockFactory)
    {
        this.Name = GetName(name, exactName);
        this._internalLock = internalLockFactory(this.Name);
    }

    internal static string GetName(string name, bool exactName)
    {
        if (name == null) { throw new ArgumentNullException(nameof(name)); }

        if (exactName)
        {
            if (name.Length > MaxNameLength) { throw new FormatException($"{nameof(name)}: must be at most {MaxNameLength} characters"); }
            // Oracle将NULL视为空字符串。请参阅 https://stackoverflow.com/questions/13278773/null-vs-empty-string-in-oracle
            if (name.Length == 0) { throw new FormatException($"{nameof(name)} must not be empty"); }
            return name;
        }

        return DistributedLockHelpers.ToSafeName(name, MaxNameLength, s => s.Length == 0 ? "EMPTY" : s);
    }

    /// <summary>
    /// 实现 <see cref="IDistributedLock.Name"/>
    /// </summary>
    public string Name { get; }

    ValueTask<OracleDistributedLockHandle?> IInternalDistributedLock<OracleDistributedLockHandle>.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken) =>
        this._internalLock.TryAcquireAsync(timeout, OracleDbmsLock.ExclusiveLock, cancellationToken, contextHandle: null).Wrap(h => new OracleDistributedLockHandle(h));

    internal static IDbDistributedLock CreateInternalLock(string name, string connectionString, Action<OracleConnectionOptionsBuilder>? options)
    {
        if (connectionString == null) { throw new ArgumentNullException(nameof(connectionString)); }

        var (keepaliveCadence, useMultiplexing) = OracleConnectionOptionsBuilder.GetOptions(options);

        if (useMultiplexing)
        {
            return new OptimisticConnectionMultiplexingDbDistributedLock(name, connectionString, OracleMultiplexedConnectionLockPool.Instance, keepaliveCadence);
        }

        return new DedicatedConnectionOrTransactionDbDistributedLock(name, () => new OracleDatabaseConnection(connectionString), useTransaction: false, keepaliveCadence);
    }

    internal static IDbDistributedLock CreateInternalLock(string name, IDbConnection connection)
    {
        if (connection == null) { throw new ArgumentNullException(nameof(connection)); }

        return new DedicatedConnectionOrTransactionDbDistributedLock(name, () => new OracleDatabaseConnection(connection));
    }
}
